home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Conversion / Convert_RTF / Source / rtfFile.m < prev    next >
Text File  |  1993-10-02  |  40KB  |  1,191 lines

  1. /***********************************************************************\
  2. RTF file class for Convert RTF which converts between Mac and NeXT rtf formats.
  3. Copyright (C) 1993 David John Burrowes
  4.  
  5. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version.
  6.  
  7. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
  8.  
  9. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  10.  
  11. The author, David John Burrowes, can be reached at:
  12.     davidjohn@kira.net.netcom.com
  13.     David John Burrowes
  14.     1926 Ivy #10
  15.     San Mateo, CA 94403-1367
  16. \***********************************************************************/
  17.  
  18. #import "rtfFile.h"
  19. #import <ctype.h>
  20. #import <string.h>
  21. #import    <stdio.h>
  22. #import "rtfToken.h"
  23.  
  24. #import <objc/List.h>    // for the list class
  25.  
  26. @implementation rtfFile
  27.  
  28.  
  29. - initAndUse:(roCString) pathname;
  30. {
  31.     [super   initAndUse: pathname];
  32.     thequeue = [[List   alloc] initCount: MAXLINELENGTH];
  33.     foundRTF = NO;
  34.     QueueHasBegin = NO;
  35.     TotalLength = 0;
  36.     textOutputLength = 0;        // 93.02.21    Added for bugfix
  37.     return self;
  38. }
  39.  
  40. -free
  41. {
  42.     [thequeue   free];
  43.     return [super   free];
  44. }
  45.  
  46.  
  47. - CloseAndSave
  48. {
  49.     [self   FlushQueue:PRETTYPRINT];
  50.     [super   CloseAndSave];
  51.     return self;
  52. }
  53.  
  54.  
  55. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  56. //    Routine:        WriteNeXTGraphicAt:WithName:Width:Height:
  57. //    Parameters:    The width and height of the graphic, the file it's in (just the name, no
  58. //                path) and a value which appears to be a dummy value these days.
  59. //    Returns:        none
  60. //    Stores:        errors
  61. //    Description:
  62. //        The NeXTGraphic control word is pretty damn weird.  So, this routine is dedicated
  63. //        to writing it out properly, so callers needn't worry about how it wants to work in
  64. //        this release. =)
  65. //        In 3.0 (and hopefully this arrangement will work OK in the future?) the format is:
  66. //        {<opt-rtf-text>{\NeXTGraphic#<1space>filename<1space>\width#<1space>
  67. //        \height#\n}\n,-or-‹}
  68. //        Now, the filename and the width anbd height make sense.  The fact that I've seen
  69. //        both comma and ‹ is strange because I note no difference between them.  Not only
  70. //        that, but remvoing them causes the picture not to display properly.  The number after
  71. //        NeXTGraphic also appears random, in that I can set it to other values and nothing
  72. //        bad seems to happen.  Until I figure out otherwise, callers are recommended to set
  73. //        this to 0.  Scott Hess has suggested, very reasonably, that it may be a character count
  74. //        (i.e this goes in as the 15th character of this document).  This requires some
  75. //        investigation.  In my experience, Edit, at least, is VERY picky about the placement of
  76. //        some of those braces, etc.  So, this will enforce the above layout, even if it isn't very
  77. //        pretty or doesn't 'go' with other stuff.
  78. //    Bugs:
  79. //    History:
  80. //        93.01.31    djb    Created
  81. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  82. - WriteNeXTGraphicAt: (PositiveInteger) loc
  83.             WithName: (CString) fileName
  84.             Width: (Integer) theWidth
  85.             Height: (Integer) theHeight
  86. {
  87.     CString    myString = NewCString(strlen(fileName)+100);  // Be lazy and do overkill.
  88.     //
  89.     //    Clear stuff out so we can write out this monster.
  90.     //
  91.     [self   FlushQueue: PRETTYPRINT];
  92.     //
  93.     //    Build the NeXTGraphic string, and write it out.
  94.     //    ***NOTE*** that we go completely behind the back of the caller.  In particular,
  95.     //    this does not update any counters or state variables if they exist.  This also
  96.     //    doesn't clip to a particular # of output columns, etc 
  97.     //
  98.     sprintf(myString, "{{\\NeXTGraphic%lu %s \\width%ld \\height%ld\n}\n‹}",
  99.         loc, fileName, theWidth, theHeight);
  100.     [self   WriteTextLine: myString];
  101.     //
  102.     //    With that dirty deed done, return.
  103.     //
  104.     FreeCString(myString);
  105.     return self;
  106. }
  107.  
  108.  
  109.  
  110.  
  111.  
  112. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  113. //    Routine:        WriteToken
  114. //    Parameters:    A token to be written
  115. //    Returns:        none
  116. //    Stores:        errors
  117. //    Description:
  118. //        This takes a token, massages the output queue if necessary, and adds the
  119. //        token to the queue to be written out.  The massaging involves flushing the
  120. //        buffer/queue if (a) this is a begin token, or if the queue will generate a line
  121. //        that is longer than we'd like of output.  After this, we just add the current
  122. //        token, and if it is an end token and there is still a { ath the head of the queue,
  123. //        we flush the queue then.
  124. //    Bugs:
  125. //    History:
  126. //        92.12.25    djb    Added tempName, so as not to be leaking the cstring to the 'rtf' test
  127. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  128. - WriteToken: theToken
  129. {
  130.     TokenType        theType        = [theToken   GetType];
  131.     PositiveInteger    deltaLength    = [theToken   GetLength];
  132.     Boolean            dontFlush    = NO;
  133.     CString            tempName;
  134.     //
  135.     //    Watch for initial {\rtf8\foo tokens.  Note the order here is very important, and
  136.     //    we rely on a don'tflush flag to ourselves from flushing right after the \rtf rathern
  137.     //    than after the \foo.  This is awkward and a bit ugly, and would be easier except
  138.     //    that the wy things are built now, one can end up with one's token being free-ed
  139.     //    while one still needs it.  Rather than fix the bug, we add a flag as a workaround.
  140.     //    (removing the flag necessitates the following if statement to be moved below after
  141.     //    the flush, which results in an error).  This whole sequence assures that the
  142.     //    {\rtf0\foo  are written on the same line 
  143.     //    This is a bit of a hack, but it works fine, in general.  It assumes, however, that
  144.     //    the only \rtf token is found at the beginning of the file (not a big deal if it doesn't,
  145.     //    just a bit weird).  It further assumes that the control word following the \rtf
  146.     //    token is the char set one.  This, also, is generaly safe.  But it IS an assumption.
  147.     //
  148.     tempName = [theToken   GetName];
  149.     //
  150.     //    Add a check to avoid trying to write out null length words, if the caller is sloppy.
  151.     //    This is a quick hack to fix a problem I just found during final beta testing.
  152.     //    It should be fixed as well in the rtf converter itself.  But it's good to have the added
  153.     //    filter here.
  154.     //
  155.     if ((strcmp(tempName, "") == 0) && (theType == tokenWord))
  156.         [theToken    free];
  157.     else
  158.     {
  159.         if (strcmp(tempName, "rtf") == 0)
  160.         {
  161.             foundRTF = YES;
  162.             dontFlush = YES;
  163.         }
  164.         //
  165.         //    Compute added length information, and flush if we have a {
  166.         //
  167.         switch (theType)
  168.         {
  169.             case tokenBeginGroup:
  170.                 [self   FlushQueue: PRETTYPRINT];
  171.                 QueueHasBegin = YES;
  172.                 deltaLength+=1;        // escape symbol ( {  )
  173.                 break;
  174.             case tokenEndGroup:
  175.                 deltaLength+=1;        // escape symbol ( } )
  176.                 break;
  177.             case tokenControlSymbol:
  178.                 deltaLength+=1;        // escape symbol ( \ )
  179.                 break;
  180.             case tokenControlWord:
  181.                 deltaLength+=2;        // escape symbol and trailing space
  182.                 break;
  183.             case tokenNoToken:
  184.             case tokenWord:
  185.                 deltaLength +=0;
  186.                 break;
  187.         }    
  188.         //
  189.         //    If our length is too long, then try to flush any leading control words or symbols.
  190.         //
  191.         if (TotalLength + deltaLength > MAXLINELENGTH)
  192.             [self   FlushPartially: PRETTYPRINT];
  193.         //
  194.         //    Flush everything (not just control words or symbols) if space is still needed.
  195.         //    and store the total lenght.  Then, store the new token.
  196.         //
  197.         if (TotalLength + deltaLength > MAXLINELENGTH)
  198.         {
  199.             [self   FlushQueue: PRETTYPRINT];
  200.             TotalLength = deltaLength;
  201.         }
  202.         else
  203.             TotalLength += deltaLength;
  204.         [thequeue   addObject: theToken];
  205.         //
  206.         //
  207.         //
  208.         if ((foundRTF == YES)  && (dontFlush == NO))
  209.         {
  210.             foundRTF = NO;
  211.             [self   FlushQueue: ASONELINE];
  212.         }
  213.         //
  214.         //    If we added a } token, and there is a { at the beginning of the buffer, flush all.
  215.         //
  216.         if ((theType == tokenEndGroup) && (QueueHasBegin == YES))
  217.             [self   FlushQueue: ASONELINE];
  218.     }
  219.     FreeCString(tempName);
  220.     return self;
  221. }
  222.  
  223.  
  224.  
  225. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  226. //    Routine:        Write:TokensAs:
  227. //    Parameters:    The number of tokens to write, and a value indicating what we should do:
  228. //        ASONELINE        Force the contents of the buffer out one the same line
  229. //        PRETTYPRINT    Write the tokens out in a nice manner
  230. //    Returns:        The type of the last token we wrote out.
  231. //    Stores:        none
  232. //    Description:
  233. //        This is the core routine for writing out information from the queue of tokens.
  234. //        you tell it how many tokens to write out, and then it will write them.  If you
  235. //        tell it to prettyprint, it does so.  If you say asoneline, it will force all the tokens on
  236. //        the queue to be written out on one line.
  237. //        This does decrement TotalLength.
  238. //        If ASONELINE is specified, no newlines are written.
  239. //        with prettyprint, newlines always follow { and } characters, as well as
  240. //        control words.  Control symbols and regular old words only get following
  241. //        newlines if they are the last token in the queue, or if they are followed by
  242. //        other types of tokens (being, end control word).
  243. //        It then returns the type of the last token.  this might allow you to do some kind
  244. //        of special post processing (e.g. if it returns a tokenWord, and you know it wasn't
  245. //        at the end of the queue, then you know there is no newline after it in the file,
  246. //        which might or might not please you)
  247. //    Bugs:
  248. //        If a control word is followed by a control word or begin or an end token, one can
  249. //        safely omit the trailing space.    But, we don't.  
  250. //        In a slightly earlier version, we weren't writing out \* tokens.  Why?
  251. //    History:
  252. //        92.12.25    djb    Added tempName to allow us to properly free cstrings we were leaking
  253. //        93.02.21    djb    Added textOutputLength stuff.  I found that chunks of text were
  254. //                    being written out as a single long line, rather than breaking
  255. //                    at most every 72 (or whatever) characters.  The new scheme keeps
  256. //                    incrementing textOutputLine whenever we write a word or a
  257. //                    controlSymbol (otherwise we set it to 0).  If we find that the line is
  258. //                    full before we write the token, we move to a new line and continue.
  259. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  260. - (TokenType) Write: (PositiveInteger)  numTokens  TokensAs: (Integer) instruction
  261. {
  262.     TokenType        theType        = tokenNoToken;
  263. //    TokenType        nextType;
  264.     Instance            theToken;
  265. //nextToken;
  266.     CString            buffer        = NewCString(1024);
  267.     PositiveInteger    tokenCount    = numTokens ;
  268.     CString            tempName;
  269.     CString            tempLoc;
  270.     PositiveInteger    numChunks;
  271.     PositiveInteger    chunkNum;
  272.     PositiveInteger    wordLength;
  273.     PositiveInteger    outputLength;
  274.     
  275.     while (tokenCount > 0)
  276.     {
  277.         //
  278.         //    Get the next token to write out, and decrement the total length of the pending
  279.         //    output line.
  280.         //
  281.         tokenCount--;
  282.         theToken = [thequeue   removeObjectAt:0];
  283.         theType = [theToken   GetType];
  284.         
  285.         TotalLength -=  [theToken   GetLength];
  286.         if (theType == tokenControlWord)
  287.             TotalLength -= 2;
  288.         else if (theType != tokenWord)
  289.             TotalLength --;
  290.         
  291.         tempName = [theToken   GetName];
  292.         //
  293.         //    93.02.21    Prepare the line based on the type of the current token (this could
  294.         //    be done in the switch further below, but this logically groups a bit differently,
  295.         //    and I figured it would be clearer if I separated it out all up here.  For some token
  296.         //    types, we reset the length of the text output line to 0 because in all these cases,
  297.         //    if we are prettyprinting, then these all finish up and move the current location
  298.         //    to the start of a new line.  If we are writing all out asoneline, then it might be
  299.         //    reasonable to increment the textoutputlength by the length of the token.  Yet,
  300.         //    this is irrelevant since the whole is to be printed on one line anyway, so I just
  301.         //    set them to 0 too.  If it is a controlsymbol or word, we check if the token will fit
  302.         //    on the current line.  If not, we start a new line, and then continue.  (the line
  303.         //    value is incremented for these below.
  304.         //
  305.         switch (theType)
  306.         {
  307.             case tokenBeginGroup:
  308.             case tokenEndGroup:
  309.             case tokenControlWord:
  310.                 //
  311.                 //    If there is text length right now, then we must move to a new line,
  312.                 //    assuming we are prettyprinting
  313.                 //
  314.                 if ((textOutputLength != 0) && (instruction != ASONELINE))
  315.                     [self  WriteTextLine: ""];
  316.                 //
  317.                 //    Always set to zero, since essentially always starting fresh after these
  318.                 //
  319.                 textOutputLength = 0;    
  320.                 break;
  321.             case tokenControlSymbol:
  322.             case tokenWord:
  323.                 //
  324.                 //    Check the length of the token, relative to the line.  If it won't fit,
  325.                 //    start a new output line. (the routines below increment the
  326.                 //    textOutputLength.  This is mainly because it would be too messy to
  327.                 //    deal with the need to break a long word up here (this has the effect,
  328.                 //    then, of always starting a very long word on it's own line.  This might
  329.                 //    result in a less than ideal output)
  330.                 //
  331.                 if (theType == tokenControlSymbol)
  332.                 {
  333.                     if (strcmp(tempName, "\'") == 0)
  334.                         outputLength = 4;
  335.                     else
  336.                         outputLength = 2;
  337.                 }
  338.                 else
  339.                     outputLength = strlen(tempName);
  340.                 if ( ((outputLength + textOutputLength) > MAXLINELENGTH)
  341.                     && (instruction != ASONELINE))
  342.                 {
  343.                     textOutputLength = 0;
  344.                     [self   WriteTextLine: ""];
  345.                 }
  346.                 break;
  347.             case tokenNoToken:
  348.                 //
  349.                 //    Do nothing.
  350.                 //
  351.                 break;
  352.         }
  353.         //
  354.         //    Write the actual token out...
  355.         //
  356.         switch (theType)
  357.         {
  358.             case tokenBeginGroup:
  359.                 QueueHasBegin = NO;    // only 1 begin in queue at a time, and this removes it
  360.                 if (instruction == ASONELINE)
  361.                     [self   WriteText: "{"];
  362.                 else
  363.                     [self   WriteTextLine: "{"];
  364.                 break;
  365.             case tokenEndGroup:
  366.                 if (instruction == ASONELINE)
  367.                     [self   WriteText: "}"];
  368.                 else
  369.                     [self   WriteTextLine: "}"];
  370.                 break;
  371.             case tokenControlWord:
  372.                 //
  373.                 //    Write out the control word appropriately, depending on whether we
  374.                 //    are writing fixed output, or pretty printing, and whether or not
  375.                 //    it has a numeric parameter.
  376.                 //
  377.                 if  (instruction == ASONELINE)
  378.                 {
  379.                     if ( [theToken   HasValue] == YES)
  380.                         [self   WriteTextUsing:  buffer
  381.                             WithFormat:  "\\%s%d ",
  382.                             tempName, [theToken   GetValue]];
  383.                     else    // no parameter
  384.                         [self   WriteTextUsing:  buffer
  385.                             WithFormat:  "\\%s ", tempName];
  386.                 }
  387.                 else
  388.                 {
  389.                     if ( [theToken   HasValue] == YES)
  390.                         [self   WriteTextUsing:  buffer
  391.                             WithFormat:  "\\%s%d\n",
  392.                             tempName, [theToken   GetValue]];
  393.                     else    // no parameter
  394.                         [self   WriteTextUsing:  buffer
  395.                             WithFormat:  "\\%s\n", tempName];
  396.                 }
  397.                 break;
  398.             case tokenControlSymbol:
  399.                 if (strcmp(tempName, "\'") == 0)
  400.                 {
  401.                     [self   WriteTextUsing:  buffer
  402.                         WithFormat:  "\\\'%.2x", [theToken   GetValue]];
  403.                     textOutputLength +=4;
  404.                 }
  405.                 else
  406.                 {
  407.                     if ( [theToken   HasValue] == YES)
  408.                         [self   WriteTextUsing:  buffer
  409.                             WithFormat:  "\\%s%d",
  410.                             tempName, [theToken   GetValue]];
  411.                     else    // no parameter
  412.                         [self   WriteTextUsing:  buffer
  413.                             WithFormat:  "\\%s", tempName];
  414.                     //
  415.                     //    This is oversimplistic.  It ignores the case of one with a
  416.                     //    parameter (which shouldn't happen anyway, though)
  417.                     //
  418.                     textOutputLength +=2;
  419.                 }
  420.                 break;
  421.             case tokenWord:
  422.                 //
  423.                 //    Check the length of the word.  In general, it wil be less than MAXLINE..
  424.                 //    chars, so we just write it out.  Otherwise, we write it out in MAX... char
  425.                 //    chunks, breaking arbitrarily as we go.
  426.                 //
  427.                 #define    MaxChunkSize    MAXLINELENGTH
  428.                 wordLength = strlen(tempName);
  429.                 if (wordLength <= MaxChunkSize)
  430.                 {
  431.                     [self   WriteText: tempName];
  432.                     textOutputLength +=wordLength;
  433.                 }
  434.                 else
  435.                 {
  436.                     tempLoc = tempName;
  437.                     numChunks = wordLength / MaxChunkSize;
  438.                     for (chunkNum = 0; chunkNum < numChunks; chunkNum++)
  439.                     {
  440.                         [self Write: MaxChunkSize BytesFrom: (ByteString) tempLoc];
  441.                         [self   WriteText: "\n"];  // kludge for a newline
  442.                         tempLoc += MaxChunkSize;
  443.                     }
  444.                     //
  445.                     //    Write any remaining bytes.
  446.                     //
  447.                     [self Write: (wordLength % MaxChunkSize)
  448.                         BytesFrom: (ByteString) tempLoc];
  449.                     textOutputLength +=(wordLength % MaxChunkSize);
  450.                 }
  451.                 break;
  452.             case tokenNoToken:
  453.                 break;
  454.         }
  455.         FreeCString(tempName);
  456.         [theToken   free];
  457.     }
  458.     FreeCString(buffer);
  459.     return theType;
  460. }
  461.  
  462.  
  463.  
  464.  
  465. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  466. //    Routine:        ClearThroughLastBegin
  467. //    Parameters:    none
  468. //    Returns:        YES if succeeded, NO if not
  469. //    Stores:        success or failure code
  470. //    Description:
  471. //        This empties current contents of the queue, if there is a begin token.
  472. //        Otherwise, it returns an error.  Tis allows the caller to backtrack and
  473. //        decide they didn't want to write out some group afterall (e.g. starting to
  474. //        write {\colortbl  only to discover that no colors were used)
  475. //    Bugs:
  476. //    History:
  477. //        92.12.13    djb    Created
  478. //        93.01.02    djb    Fixed bug of not clearing QueueHasBegin after clearing!
  479. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  480. - (Boolean) ClearThroughLastBegin
  481. {
  482.     Boolean            result        = NO;
  483.     PositiveInteger    tokenCount    = [thequeue count];
  484.     PositiveInteger    index        = 0;
  485.     Instance            temptoken;
  486.  
  487.     if (QueueHasBegin == YES)
  488.     {
  489.         //
  490.         //    There's a begin here, so just rip out the N tokens, and free them
  491.         //
  492.         for (index = 0; index < tokenCount; index++)
  493.         {
  494.             temptoken =  [thequeue   removeObjectAt:0];
  495.             [temptoken  free];
  496.         }
  497.         QueueHasBegin = NO;
  498.  
  499.         result = YES;
  500.         [self   StoreErrorCode: ERR_OK
  501.             AndText: "Succeded in flushing back to { token"];
  502.     }
  503.     else
  504.     {
  505.         result = NO;
  506.         [self   StoreErrorCode: ERR_NOBEGIN
  507.             AndText: "Unable to find begin token.  Could not clear"];
  508.     }
  509.     return result;
  510. }
  511.  
  512.  
  513.  
  514. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  515. //    Routine:        FlushPartially
  516. //    Parameters:    A value indicating what we should do:
  517. //        ASONELINE    Force the contents of the buffer out one the same line
  518. //        PRETTYPRINT    Write the tokens out in a nice manner
  519. //    Returns:        none
  520. //    Stores:        none
  521. //    Description:
  522. //        This is much like FlushQueue, below, with one exception,  It counts back from
  523. //        the end of the queue to find the last non-word or control symbol token in the
  524. //        queue.  Then, it writes everyting up  (and including) to that out.
  525. //        This allows us to keep any word tokens on the queue so they have the chance to
  526. //        be written out with other word tokens that might be added.
  527. //    Bugs:
  528. //        If a control word is followed by a control word or space or an end token, one can
  529. //        safely omit the trailing space.  
  530. //    History:
  531. //        93.02.21    djb    Added textOutputLength alteration.  See Write:TokenAs: for
  532. //                    more details.
  533. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  534. -FlushPartially: (Integer) instruction
  535. {
  536.     Instance        theToken;
  537.     Boolean        seenNonWord    = NO;
  538.     TokenType    theType;
  539.  
  540.     PositiveInteger    tokenNum = [thequeue count] ;
  541.     
  542.     while ( (seenNonWord == NO)  && (tokenNum > 0) )
  543.     {
  544.         tokenNum --;
  545.         theToken = [thequeue objectAt: tokenNum];
  546.         if (theToken != nil)
  547.         {
  548.             theType = [theToken    GetType];
  549.             if ( (theType != tokenWord) && (theType != tokenControlSymbol) )
  550.                 seenNonWord = YES;
  551.         }
  552.     }
  553.     if (seenNonWord == YES)
  554.         [self   Write: tokenNum+1 TokensAs: instruction];
  555.  
  556.     if (instruction == ASONELINE)
  557.     {
  558.         [self   WriteText: "\n"];
  559.         textOutputLength = 0;        // 93.02.21 For bugfix
  560.     }
  561.     return self;
  562. }
  563.  
  564.  
  565.  
  566.  
  567. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  568. //    Routine:        FlushQueue
  569. //    Parameters:    A value indicating what we should do:
  570. //        ASONELINE        Force the contents of the buffer out one the same line
  571. //        PRETTYPRINT    Write the tokens out in a nice manner
  572. //    Returns:        none
  573. //    Stores:        none
  574. //    Description:
  575. //        This calls Write:TokensAs: to write all the tokens in the queue out.  Aside from
  576. //        that, this doesn't so much anymore. 
  577. //    Bugs:
  578. //    History:
  579. //        93.02.21    djb    Added textOutputLength alteration.  See Write:TokenAs: for
  580. //                    more details.
  581. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  582. - FlushQueue: (Integer) instruction
  583. {
  584.     [self   Write: [thequeue   count]  TokensAs: instruction];
  585.     
  586.     if (instruction == ASONELINE)
  587.     {
  588.         [self   WriteText: "\n"];
  589.         textOutputLength = 0;        // 93.02.21 For bugfix
  590.     }
  591.     TotalLength = 0;
  592.     return self;
  593. }
  594.  
  595.  
  596.  
  597. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  598. //    Routine:        Hint
  599. //    Parameters:    an item from the enumerated type HintName
  600. //    Returns:        self
  601. //    Stores:        none
  602. //    Description:
  603. //        This is an experiment with implementing a 'hint' method which I've been
  604. //        thinking about.  I'm sure it's not quite right, but I gotta implement it to
  605. //        figure out what's wrong.  The purpose of the method is simple.  It allows the
  606. //        caller to give the instance suggestions about how it might want to behave.
  607. //        Thus, it allows, perhaps, chances to tweak performance.  But it should not
  608. //        cause the output to be altered in any way.
  609. //        At the moment, this only recognizes two hints: that forthcomming word tokens
  610. //        are picture data, and that they aren't.
  611. //    Bugs:
  612. //    History:
  613. //        92.12.24    djb    created
  614. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  615.  
  616. - Hint: (HintName) theHint;
  617. {
  618.     switch (theHint)
  619.     {
  620.         case WordsArePictures:
  621.             PictureHint = theHint;
  622.             break;
  623.         case WordsAreNotPictures:
  624.             PictureHint = theHint;
  625.             break;
  626.         case NoHint:
  627.             break;
  628.     }
  629.     return self;
  630. }
  631.  
  632.         
  633.  
  634. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  635. //    Routine:        GetToken
  636. //    Parameters:    none
  637. //    Returns:        The token that was read
  638. //    Stores:        The token we filled
  639. //                true if we filled it, false if not
  640. //    Description:
  641. //        When called, this tries to get a new token from the file. If we have not reached
  642. //        EOF, we read the next character, and use it to determine what type of token
  643. //        is next.  A \ introduces a control word or symbol, a { a begin group, } an end
  644. //        group, and anything else introduces a word.  Call proper routines to read each in.
  645. //    Bugs:
  646. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  647.  
  648. - GetToken
  649. {
  650.     Character    theChar;
  651.     Instance        theToken    = NullInstance;
  652.  
  653.     [self   ResetResults];
  654.  
  655.     if (FileIsOpen == NO )
  656.     {
  657.         [self    StoreErrorCode: ERR_FILENOTOPEN AndText: "Can't read from closed file"];
  658.         theToken = NullInstance;
  659.     }
  660.     else
  661.     {
  662.         //
  663.         //    Loop until:
  664.         //        (1) the end of file is reached
  665.         //        (2) a token is found,
  666.         //
  667.         do
  668.         {
  669.             //
  670.             //    If we are comming through here a second time (the token was a null
  671.             //    token the last time though, for instance), then free the token...
  672.             //
  673.             if (theToken != NullInstance)
  674.                 [theToken  free];
  675.             //
  676.             //    Get the next character, and use it to decide what kind of token is next.
  677.             //
  678.             theChar = [self LookAtNextCharacter];
  679.             switch (theChar)
  680.             {
  681.                 case '{' :
  682.                     theToken = [self GetOpenBraceToken];
  683.                     break;
  684.                 case '}' :
  685.                     theToken =[self GetCloseBraceToken];
  686.                     break;
  687.                 case '\\' :
  688.                     theToken = [self GetControlToken];
  689.                     break;
  690.                 default :
  691.                     theToken = [self GetWordToken];
  692.                     break;
  693.             }
  694.         }
  695.         while (([theToken GetType] == tokenNoToken) && (FileLocation != fileAtEOF));
  696.         //
  697.         //    No real error codes to process here.  
  698.         //
  699.     }
  700.  
  701.     if (FileLocation == fileAtEOF)
  702.         [self   StoreErrorCode: ERR_EOF AndText: "We found the end of file, dude."];
  703.     else
  704.         [self   StoreErrorCode: ERR_OK AndText: "Everything went GREAT!"];
  705.     
  706.     return theToken;
  707. }
  708.  
  709.  
  710. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  711. //    Routine:        GetNextControlToken
  712. //    Parameters:    none
  713. //    Returns:        The token that was read
  714. //    Stores:        true if we filled it, false if not
  715. //    Description:
  716. //        This routine simply walks quickly through the file, looking for the next
  717. //        control word or symbol token.  It ignores everything else.  When it finds the
  718. //        start of one, it returns it to the caller.If it fines eof, it returns a null token and
  719. //        an error.
  720. //        This allows a caller to read in all the control token info more efficiently than caling
  721. //        GetNextToken.
  722. //    Bugs:
  723. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  724.  
  725. - GetNextControlToken
  726. {
  727.     Character    theChar;
  728.     Instance        theToken;
  729.  
  730.     [self   ResetResults];
  731.  
  732.     if (FileIsOpen == NO )
  733.     {
  734.         [self    StoreErrorCode: ERR_FILENOTOPEN AndText: "Can't read from closed file"];
  735.         theToken = NullInstance;
  736.     }
  737.     else
  738.     {
  739.         do
  740.         {
  741.             theChar = [self ReadByte];
  742.         }
  743.         while ((theChar != '\\') && (FileLocation != fileAtEOF));
  744.         if (theChar == '\\')
  745.         {
  746.             [self   UnGetCharacter];
  747.             theToken = [self GetControlToken];
  748.         }
  749.         else
  750.             theToken = NullInstance;
  751.     }
  752.  
  753.     if (FileLocation == fileAtEOF)
  754.         [self   StoreErrorCode: ERR_EOF AndText: "We found the end of file, dude."];
  755.     else
  756.         [self   StoreErrorCode: ERR_OK AndText: "Everything went GREAT!"];
  757.     
  758.     return theToken;
  759. }
  760.  
  761. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  762. //    Routine:        GetOpenBraceToken
  763. //    Parameters:    none
  764. //    Returns:        The token that was read
  765. //    Stores:        none
  766. //    Description:
  767. //        This simply initalizes a begin-group token Instance, and returns it.  No biggie.
  768. //        We then discard the { that is waiting.
  769. //    Bugs:
  770. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  771. - GetOpenBraceToken
  772. {
  773.     Instance        theToken;
  774.     Character    theCharacter = [self GetCharacter];
  775.     
  776.     if (theCharacter == '\{')
  777.     {
  778.         theToken = [[rtfToken alloc] initTokenOfType: tokenBeginGroup];
  779.         [self   StoreErrorCode: ERR_OK AndText: "OK"];
  780.     }
  781.     else
  782.     {
  783.         theToken = [[rtfToken alloc] initTokenOfType: tokenNoToken];
  784.         [self   UnGetCharacter];
  785.         [self   StoreErrorCode: ERR_BADCHAR
  786.             AndText: "That was not my type of character"];
  787.     }
  788.     
  789.     return theToken;
  790. }
  791.  
  792.  
  793. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  794. //    Routine:        GetCloseBraceToken
  795. //    Parameters:    none
  796. //    Returns:        The token that was read
  797. //    Stores:        none
  798. //    Description:
  799. //        This simply initalizes a begin-group token Instance, and returns it.  No biggie.
  800. //        We then discard the } that is waiting.
  801. //    Bugs:
  802. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  803. - GetCloseBraceToken
  804. {
  805.     Instance        theToken;
  806.     Character    theCharacter = [self GetCharacter];
  807.     
  808.     if (theCharacter == '}')
  809.     {
  810.         theToken = [[rtfToken alloc] initTokenOfType: tokenEndGroup];
  811.         [self   StoreErrorCode: ERR_OK AndText: "OK"];
  812.     }
  813.     else
  814.     {
  815.         theToken = [[rtfToken alloc] initTokenOfType: tokenNoToken];
  816.         [self   UnGetCharacter];
  817.         [self   StoreErrorCode: ERR_BADCHAR
  818.             AndText: "That was not my type of character"];
  819.     }
  820.     
  821.     return theToken;
  822. }
  823.  
  824.  
  825.  
  826. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  827. //    Routine:        GetControlSymbolToken
  828. //    Parameters:    none
  829. //    Returns:        The token that was read
  830. //    Stores:        error
  831. //    Description:
  832. //        Reads in a control token.  If the character following the \ is not a letter, it is a
  833. //        control symbol.  A control symbol has no delimiter, Since, by definition,
  834. //        it is 1 character long (aside from the \ escape).  (if the sequence is undefined, Word
  835. //        appears to simply ignore the control symbol (or word)). Note that \' is unique in
  836. //        that it does take a parameter (a 2 char hex value).
  837. //        Otherwise, we have a control word, which is a sequence of letters
  838. //        followed immediately, in some cases, by a numberic parameter.
  839. //    Bugs:
  840. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  841.  
  842. - GetControlToken
  843. {
  844.     //
  845.     //    Read in the symbol character.
  846.     //
  847.     Character        theChar,
  848.                     theValue;
  849.     PositiveInteger    nameBufferSize    = 15;
  850.     PositiveInteger    nameBufferIncrement    = 15;
  851.     PositiveInteger    nameIndex;
  852.     CString            tempName,
  853.                     tokenName = NewCString(nameBufferSize);
  854.     Instance            theToken;
  855.     Integer            theNumber;
  856.     
  857.     [self   ResetResults];
  858.     
  859.     theChar = [self GetCharacter];
  860.     if (theChar != '\\')
  861.     {
  862.         [self   StoreErrorCode: ERR_BADCHAR
  863.             AndText: "The next value in the file was NOT a control token"];
  864.         [self   UnGetCharacter];
  865.         theToken = [[rtfToken alloc] initTokenOfType: tokenNoToken];
  866.     }
  867.     else
  868.     {
  869.         theChar = [self GetCharacter];
  870.         if (isalpha(theChar)== 0)
  871.         {
  872.             theToken = [[rtfToken alloc] initTokenOfType: tokenControlSymbol];
  873.             //
  874.             //    Process a control symbol  \?
  875.             //
  876.             sprintf (tokenName, "%c", theChar);
  877.             [theToken SetTokenName: tokenName];
  878.             //
  879.             //    If the token is \', then get it's hex parameter..
  880.             //
  881.             if (theChar == '\'')    // if it's the special one
  882.             {
  883.                 theValue = [self GetHexByte]; 
  884.                 // @@ IGNORING ERROR CODE!!
  885.                 [theToken SetTokenValue: theValue];
  886.             }
  887.         }
  888.         else
  889.         {
  890.             theToken = [[rtfToken alloc] initTokenOfType: tokenControlWord];
  891.             //
  892.             //    Process a control word
  893.             //
  894.             nameIndex = 0;
  895.             while ((isalpha(theChar) != 0) && (FileLocation != fileAtEOF))
  896.             {
  897.                 tokenName[nameIndex] = theChar;
  898.                 if (nameIndex >= nameBufferSize)
  899.                 {
  900.                     //
  901.                     //    Allocate more space for the name!
  902.                     //
  903.                     tempName = NewCString(nameBufferSize + nameBufferIncrement);
  904.                     strncpy(tempName, tokenName, nameBufferSize);
  905.                     nameBufferSize +=nameBufferIncrement;
  906.                     FreeCString(tokenName);
  907.                     tokenName = tempName;
  908.                 }
  909.                 theChar = [self   GetCharacter];
  910.                 nameIndex++;
  911.             }
  912.             tokenName[nameIndex] = NullCharacter;
  913.             [theToken SetTokenName: tokenName];
  914.             //
  915.             //    A control word token can be terminated by a space which is consumed.
  916.             //    f we were not, then we return the character to the file.
  917.             //    93.02.21    Modified checking for terminating conditions.  We have so far
  918.             //    accumulated \foo.  If the next character is a space, we consume it and
  919.             //    proceed.  Otherwise, we replace it in the stream.  If it was, however, a
  920.             //    digit, we read in the number, and consume the space that follows that, if
  921.             //    any. (before this, we were not explicitly checking for digits, and so it would
  922.             //    consider a newline between \foo and thenumber to be no big deal!
  923.             //    93.02.21    Fixed a problem I just introduced.  Now, we check if the next char
  924.             //    is a - as well as whether it is a digit.  
  925.             //
  926.             if (theChar != ' ')
  927.             {
  928.                 [self   UnGetCharacter];
  929.                 if  ((isdigit(theChar) != 0) || (theChar == '-'))
  930.                 {
  931.                     theNumber = [self GetNumber];
  932.                     if ([self   GetErrorCode] == ERR_OK)
  933.                         [theToken SetTokenValue: theNumber];
  934.                     if ([self   LookAtNextCharacter] == ' ')
  935.                         [self   GetCharacter];
  936.                 }
  937.             }
  938.         }
  939.         //
  940.         //    Check that ALL went well to this poi.
  941.         //
  942.     }
  943.     FreeCString(tokenName);
  944.     return theToken;
  945. }
  946.  
  947.         
  948.  
  949.  
  950.  
  951.  
  952. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  953. //    Routine:        GetWordToken
  954. //    Parameters:    none
  955. //    Returns:        The token that was read
  956. //    Stores:        error
  957. //    Description:
  958. //        This reads in the next token from the rtf file.  Since we were called by something
  959. //        that presumably knows what it's doing, we assume that the next token IS a word
  960. //        (if we notice it isn't, we return an error, of course)
  961. //        NOTE: spaces are 'legal' components of a Word token IFF they are at the
  962. //        beginning before any non-white characters appear.
  963. //    Bugs:
  964. //    History:
  965. //        92.12.24    djb    Modified to regonize the hints for 'WordsArePictures'
  966. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  967.  
  968. - GetWordToken
  969. {
  970.     Boolean            terminatingWhite = NO, // true if a white space found after the word
  971.                     nonWhiteRead = NO;    // true if we  have read any non-white characters
  972.     PositiveInteger    nameIndex = 0;
  973.     PositiveInteger    nameBufferSize;
  974.     PositiveInteger    nameBufferIncrement;
  975.     CString            tempName,
  976.                     name;
  977.     Character        theChar = [self GetCharacter];
  978.     Instance            theToken;
  979.  
  980.     [self   ResetResults];
  981.     //
  982.     //    Allocate space for the name of this word token, and how much we should increment
  983.     //    the buffer by if we run out of room.  If we suspect we're going to be reading in a
  984.     //    picture, allocate a HECK of a lot more space for the token than normal. 
  985.     //    Use one less than power of 2 so it and null byte take are a power.
  986.     //
  987.     if ( PictureHint == WordsArePictures)
  988.     {
  989.         nameBufferSize = (32*1024) -1; // Allocate 32K, (will hold 16K worth of pict)
  990.         nameBufferIncrement = (32*1024) - 1;
  991.     }
  992.     else
  993.     {
  994.         nameBufferSize = 15;
  995.         nameBufferIncrement = 63;
  996.     }
  997.     name = NewCString(nameBufferSize);
  998.     //
  999.     //    Set the nonWhite flag to true if it happens that our first character is not a space.
  1000.     //    Also, ignore the character if it is an end of line char.
  1001.     //
  1002.     if ((theChar != ' ')  && (theChar != '\r') && (theChar != '\n'))
  1003.         nonWhiteRead = YES;
  1004.     //
  1005.     //    As long as we haven't found a delimiter, read new characters    //
  1006.     while ((theChar != '{')     &&            // the character isn't an open-group
  1007.             (theChar != '\\')     &&            //nor is it an escape
  1008.             (theChar != '}')        &&        // nor is it a close group
  1009.             (terminatingWhite != YES)    &&    //we haven't found a white space after the word
  1010.             (FileLocation != fileAtEOF))    // the end of file has not been reached (BAAAD)
  1011.     {
  1012.         //
  1013.         //    If the character is a CR or LF, skip it, Otherwise store the char.
  1014.         //
  1015.         if ((theChar != '\r') && (theChar != '\n'))
  1016.         {
  1017.             name[nameIndex] = theChar;
  1018.             nameIndex++;
  1019.             if (nameIndex >= nameBufferSize)
  1020.             {
  1021.                 //
  1022.                 //    Allocate more space for the name!
  1023.                 //
  1024.                 tempName = NewCString(nameBufferSize + nameBufferIncrement);
  1025.                 strncpy(tempName, name, nameBufferSize);
  1026.                 tempName[nameBufferSize] = NullCharacter;
  1027.                 nameBufferSize +=nameBufferIncrement;
  1028.                 FreeCString(name);
  1029.                 name = tempName;
  1030.             }
  1031.         }
  1032.         //
  1033.         //    Get the next character.  If we've already read a non-white character, and just
  1034.         //    read a white one, then it serves as a delimiter...        //
  1035.         theChar = [self GetCharacter];
  1036.         if (theChar == ' ')
  1037.         {
  1038.             if (nonWhiteRead == YES)
  1039.                 terminatingWhite = YES;
  1040.         }
  1041.         else
  1042.             nonWhiteRead = YES;
  1043.     }
  1044.  
  1045.     //    
  1046.     //    If we were delimited by white space, or the escape or begin-end-group chars
  1047.     //    then unget the character.    
  1048.     //
  1049.     if ((terminatingWhite == YES) ||
  1050.         (theChar == '\{')     ||
  1051.         (theChar == '\\')     ||
  1052.         (theChar == '}'))
  1053.     [self   UnGetCharacter];
  1054.  
  1055.     name[nameIndex] = NullCharacter;
  1056.  
  1057.     if (nameIndex != 0)
  1058.     {
  1059.         theToken = [[rtfToken alloc] initTokenOfType: tokenWord];
  1060.         [theToken   SetTokenName: name];
  1061.     }
  1062.     else
  1063.     {
  1064.         //
  1065.         //    Check for a null token (possible if, say, we fould only CR or LF before
  1066.         //    a control word, or if the first thing we found was a delimiter)
  1067.         //
  1068.         theToken = [[rtfToken alloc] initTokenOfType: tokenNoToken];
  1069.         [self   StoreErrorCode: ERR_NOTOKENFOUND AndText: "A null Word token found"];
  1070.     }
  1071.  
  1072.     FreeCString(name);
  1073.     return theToken;
  1074. }
  1075.  
  1076.  
  1077.  
  1078.  
  1079. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1080. //    Routine:        GetFamilyNameToken
  1081. //    Parameters:    none
  1082. //    Returns:        The token that was read
  1083. //    Stores:        error
  1084. //    Description:
  1085. //        This is a specialized version of the GetWordToken call.  It exists because it turns
  1086. //        out that at least MS Word is happy to break the name of a type family across
  1087. //        lines, and assumes the end of line character will be treated as a space (that is,
  1088. //        in all other parts of the file, a line ending should be treated as if it doesn't exist.
  1089. //        In this case, it might mean there should be a space there).  This simply does
  1090. //        the actions of reading in all the characters until a { or \ or } is found (hopefully,
  1091. //        it will always be the last!!!  Perhaps we should even be checking for the errors
  1092. //        from it.  We return the whole to the caller as a word token.
  1093. //    Bugs:
  1094. //    History:
  1095. //        92.12.24    djb    Modified to use namebuffer and namebufferincrement
  1096. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1097.  
  1098. - GetFamilyNameToken
  1099. {
  1100.     PositiveInteger    nameIndex = 0;
  1101.     PositiveInteger    nameBufferSize    = 31;
  1102.     PositiveInteger    nameBufferIncrement    = 15;
  1103.     CString            tempName,
  1104.                     name;
  1105.     Character        theChar = [self GetCharacter];
  1106.     Instance            theToken;
  1107.  
  1108.     [self   ResetResults];
  1109.     //
  1110.     //    Allocate space for the name of family name
  1111.     //
  1112.     name = NewCString(nameBufferSize);
  1113.     //
  1114.     //    As long as we haven't found a delimiter, read new characters    //
  1115.     while ((theChar != '{')     &&            // the character isn't an open-group
  1116.             (theChar != '\\')     &&            //nor is it an escape
  1117.             (theChar != '}')        &&        // nor is it a close group
  1118.             (theChar != ';')    &&            // special because it can terminate a family name
  1119.             (FileLocation != fileAtEOF))    // the end of file has not been reached (BAAAD)
  1120.     {
  1121.         //
  1122.         //    If the character is a CR or LF, skip it, Otherwise store the char.
  1123.         //
  1124.         if ((theChar== '\r') || (theChar == '\n'))
  1125.             name[nameIndex] = ' ';
  1126.         else
  1127.             name[nameIndex] = theChar;
  1128.         nameIndex++;
  1129.         if (nameIndex >=  nameBufferSize)
  1130.         {
  1131.             //
  1132.             //    Allocate more space for the name!
  1133.             //
  1134.             tempName = NewCString(nameBufferSize + nameBufferIncrement);
  1135.             strncpy(tempName, name, nameBufferSize);
  1136.             tempName[nameBufferSize] = NullCharacter;
  1137.             nameBufferSize +=nameBufferIncrement;
  1138.             FreeCString(name);
  1139.             name = tempName;
  1140.         }
  1141.         //
  1142.         //    Get the next character.  If we've already read a non-white character, and just
  1143.         //    read a white one, then it serves as a delimiter...        //
  1144.         theChar = [self GetCharacter];
  1145.     }
  1146.  
  1147.     if (theChar == ';')
  1148.     {
  1149.         //
  1150.         //    consume any remaining spaces
  1151.         //
  1152.         do
  1153.         {
  1154.             theChar = [self   GetCharacter];
  1155.         }
  1156.         while ( theChar == ' ');
  1157.         [self   UnGetCharacter];
  1158.     }
  1159.     //    
  1160.     //    If we were delimited by white space, or the escape or begin-end-group chars
  1161.     //    then unget the character.    
  1162.     //
  1163.     else if ((theChar == '\{')     ||
  1164.         (theChar == '\\')     ||
  1165.         (theChar == '}'))
  1166.     [self   UnGetCharacter];
  1167.  
  1168.     name[nameIndex] = NullCharacter;
  1169.  
  1170.     if (strlen(name) != 0)
  1171.     {
  1172.         theToken = [[rtfToken alloc] initTokenOfType: tokenWord];
  1173.         [theToken   SetTokenName: name];
  1174.     }
  1175.     else
  1176.     {
  1177.         //
  1178.         //    Check for a null token (possible if, say, we fould only CR or LF before
  1179.         //    a control word, or if the first thing we found was a delimiter)
  1180.         //
  1181.         theToken = [[rtfToken alloc] initTokenOfType: tokenNoToken];
  1182.         [self   StoreErrorCode: ERR_NOTOKENFOUND AndText: "A null Word token found"];
  1183.     }
  1184.  
  1185.     FreeCString(name);
  1186.     return theToken;
  1187. }
  1188.  
  1189.  
  1190.  
  1191. @end